/*-------------------------------------------------------------------------
 *
 * misc.c
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/misc.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <math.h>
#include <unistd.h>

#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
#include "commands/dbcommands.h"
#include "commands/tablespace.h"
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"

/*
 * num_nulls()和num_nonnulls()的通用子例程。
 * 如果成功则返回true，如果函数应返回NULL则返回false。
 * 如果成功，总参数计数和NULL的数量将返回到*nargs和*nulls。
 */
static bool fc_count_nulls(FunctionCallInfo fcinfo,
			int32 *fc_nargs, int32 *fc_nulls)
{
	int32		fc_count = 0;
	int			fc_i;

	/* 我们是否得到了一个可变参数数组参数，或单独的参数？ */
	if (get_fn_expr_variadic(fcinfo->flinfo))
	{
		ArrayType  *fc_arr;
		int			fc_ndims,
					fc_nitems,
				   *fc_dims;
		bits8	   *fc_bitmap;

		Assert(PG_NARGS() == 1);

		/*
		 * 如果我们得到一个为NULL的可变参数数组参数，我们无法对元素的数量
		 * 做出有用的说法，所以返回NULL。该行为
		 * 与其他可变函数一致 - 见concat_internal。
		 */
		if (PG_ARGISNULL(0))
			return false;

		/*
		 * 非NULL参数最好是一个数组。我们假设任何可以使get_fn_expr_variadic
		 * 返回true的调用上下文都将检查一个以VARIADIC标记的参数实际上是一个数组。
		 * 所以直接Assert它是一个数组而不是进行全面的错误检查应该是可以的。
		 */
		Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 0))));

		/* 好的，可以安全地获取数组值 */
		fc_arr = PG_GETARG_ARRAYTYPE_P(0);

		/* 计算数组元素 */
		fc_ndims = ARR_NDIM(fc_arr);
		fc_dims = ARR_DIMS(fc_arr);
		fc_nitems = ArrayGetNItems(fc_ndims, fc_dims);

		/* 计算那些为NULL的元素 */
		fc_bitmap = ARR_NULLBITMAP(fc_arr);
		if (fc_bitmap)
		{
			int			fc_bitmask = 1;

			for (fc_i = 0; fc_i < fc_nitems; fc_i++)
			{
				if ((*fc_bitmap & fc_bitmask) == 0)
					fc_count++;

				fc_bitmask <<= 1;
				if (fc_bitmask == 0x100)
				{
					fc_bitmap++;
					fc_bitmask = 1;
				}
			}
		}

		*fc_nargs = fc_nitems;
		*fc_nulls = fc_count;
	}
	else
	{
		/* 单独的参数，因此只需计数 */
		for (fc_i = 0; fc_i < PG_NARGS(); fc_i++)
		{
			if (PG_ARGISNULL(fc_i))
				fc_count++;
		}

		*fc_nargs = PG_NARGS();
		*fc_nulls = fc_count;
	}

	return true;
}

/*
 * num_nulls()
 *	计算NULL参数的数量
 */
Datum pg_num_nulls(PG_FUNCTION_ARGS)
{
	int32		fc_nargs,
				fc_nulls;

	if (!fc_count_nulls(fcinfo, &fc_nargs, &fc_nulls))
		PG_RETURN_NULL();

	PG_RETURN_INT32(fc_nulls);
}

/*
 * num_nonnulls()
 *	计算非NULL参数的数量
 */
Datum pg_num_nonnulls(PG_FUNCTION_ARGS)
{
	int32		fc_nargs,
				fc_nulls;

	if (!fc_count_nulls(fcinfo, &fc_nargs, &fc_nulls))
		PG_RETURN_NULL();

	PG_RETURN_INT32(fc_nargs - fc_nulls);
}


/*
 * current_database()
 *	将当前数据库暴露给用户
 */
Datum current_database(PG_FUNCTION_ARGS)
{
	Name		fc_db;

	fc_db = (Name) palloc(NAMEDATALEN);

	namestrcpy(fc_db, get_database_name(MyDatabaseId));
	PG_RETURN_NAME(fc_db);
}


/*
 * current_query()
 *	将当前查询暴露给用户（在存储过程中特别有用）
 *	我们可能希望有一天使用ActivePortal->sourceText。
 */
Datum current_query(PG_FUNCTION_ARGS)
{
	/* 没有简单的方法访问更简洁的 'query_string' */
	if (debug_query_string)
		PG_RETURN_TEXT_P(cstring_to_text(debug_query_string));
	else
		PG_RETURN_NULL();
}

/* 查找哪些数据库使用表空间的函数 */

Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
{
	Oid			fc_tablespaceOid = PG_GETARG_OID(0);
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	char	   *fc_location;
	DIR		   *fc_dirdesc;
	struct dirent *fc_de;

	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);

	if (fc_tablespaceOid == GLOBALTABLESPACE_OID)
	{
		ereport(WARNING,
				(errmsg("global tablespace never has databases")));
		/* 返回空的元组存储 */
		return (Datum) 0;
	}

	if (fc_tablespaceOid == DEFAULTTABLESPACE_OID)
		fc_location = psprintf("base");
	else
		fc_location = psprintf("pg_tblspc/%u/%s", fc_tablespaceOid,
							TABLESPACE_VERSION_DIRECTORY);

	fc_dirdesc = AllocateDir(fc_location);

	if (!fc_dirdesc)
	{
		/* 唯一预期的错误是 ENOENT */
		if (errno != ENOENT)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not open directory \"%s\": %m",
							fc_location)));
		ereport(WARNING,
				(errmsg("%u is not a tablespace OID", fc_tablespaceOid)));
		/* 返回空的元组存储 */
		return (Datum) 0;
	}

	while ((fc_de = ReadDir(fc_dirdesc, fc_location)) != NULL)
	{
		Oid			fc_datOid = atooid(fc_de->d_name);
		char	   *fc_subdir;
		bool		fc_isempty;
		Datum		fc_values[1];
		bool		fc_nulls[1];

		/* 该测试跳过 . 和 ..，但非常脆弱 */
		if (!fc_datOid)
			continue;

		/* 如果数据库子目录为空，不要报告表空间被使用 */

		fc_subdir = psprintf("%s/%s", fc_location, fc_de->d_name);
		fc_isempty = directory_is_empty(fc_subdir);
		pfree(fc_subdir);

		if (fc_isempty)
			continue;			/* 确实，里面什么都没有 */

		fc_values[0] = ObjectIdGetDatum(fc_datOid);
		fc_nulls[0] = false;

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	FreeDir(fc_dirdesc);
	return (Datum) 0;
}


/*
 * pg_tablespace_location - 获取表空间的位置
 */
Datum pg_tablespace_location(PG_FUNCTION_ARGS)
{
	Oid			fc_tablespaceOid = PG_GETARG_OID(0);
	char		fc_sourcepath[MAXPGPATH];
	char		fc_targetpath[MAXPGPATH];
	int			fc_rllen;
	struct stat fc_st;

	/*
	 * 将此函数应用于 pg_class.reltablespace 是有用的，
	 * 其中零表示“数据库的默认表空间”。所以，我们选择
	 * 假定零的意思是这样，而不是抛出错误。
	 */
	if (fc_tablespaceOid == InvalidOid)
		fc_tablespaceOid = MyDatabaseTableSpace;

	/*
	 * 对于集群的默认表空间返回空字符串
	 */
	if (fc_tablespaceOid == DEFAULTTABLESPACE_OID ||
		fc_tablespaceOid == GLOBALTABLESPACE_OID)
		PG_RETURN_TEXT_P(cstring_to_text(""));

#if defined(HAVE_READLINK) || defined(WIN32)

	/*
	 * 通过读取在 pg_tblspc/<oid> 中的符号链接找到表空间的位置。
	 */
	snprintf(fc_sourcepath, sizeof(fc_sourcepath), "pg_tblspc/%u", fc_tablespaceOid);

	/*
	 * 在读取链接之前，检查源路径是否是链接或
	 * 连接点。注意，针对启用 allow_in_place_tablespaces 创建的表空间，目录是可能的。
	 * 如果发现目录，则返回到数据目录的相对路径。
	 */
	if (lstat(fc_sourcepath, &fc_st) < 0)
	{
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m",
						fc_sourcepath)));
	}

	if (!S_ISLNK(fc_st.st_mode))
		PG_RETURN_TEXT_P(cstring_to_text(fc_sourcepath));

	/*
	 * 如果存在链接或连接点，返回指向的路径。
	 */
	fc_rllen = readlink(fc_sourcepath, fc_targetpath, sizeof(fc_targetpath));
	if (fc_rllen < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read symbolic link \"%s\": %m",
						fc_sourcepath)));
	if (fc_rllen >= sizeof(fc_targetpath))
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("symbolic link \"%s\" target is too long",
						fc_sourcepath)));
	fc_targetpath[fc_rllen] = '\0';

	PG_RETURN_TEXT_P(cstring_to_text(fc_targetpath));
#else
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("tablespaces are not supported on this platform")));
	PG_RETURN_NULL();
#endif
}

/*
 * pg_sleep - 延迟 N 秒
 */
Datum pg_sleep(PG_FUNCTION_ARGS)
{
	float8		fc_secs = PG_GETARG_FLOAT8(0);
	float8		fc_endtime;

	/*
	 * 我们使用 WaitLatch 睡眠，以确保如果有
	 * 重要信号（例如 SIGALRM 或 SIGINT）到达，我们会及时醒来。
	 * 因为 WaitLatch 的最大延迟限制是 INT_MAX 毫秒，而用户
	 * 可能请求超过这个时间，所以我们最多睡 10 分钟，然后
	 * 循环。
	 *
	 * 通过最初计算预定停止时间，我们避免了在多次睡眠中
	 * 累积额外延迟。这也确保了当 WaitLatch 被
	 * 非查询取消信号（如 SIGHUP）提前终止时，我们不会
	 * 延迟少于指定时间。
	 */
#define GetNowFloat()	((float8) GetCurrentTimestamp() / 1000000.0)

	fc_endtime = GetNowFloat() + fc_secs;

	for (;;)
	{
		float8		fc_delay;
		long		fc_delay_ms;

		CHECK_FOR_INTERRUPTS();

		fc_delay = fc_endtime - GetNowFloat();
		if (fc_delay >= 600.0)
			fc_delay_ms = 600000;
		else if (fc_delay > 0.0)
			fc_delay_ms = (long) ceil(fc_delay * 1000.0);
		else
			break;

		(void) WaitLatch(MyLatch,
						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
						 fc_delay_ms,
						 WAIT_EVENT_PG_SLEEP);
		ResetLatch(MyLatch);
	}

	PG_RETURN_VOID();
}

/* 返回语法关键字的列表的函数 */
Datum pg_get_keywords(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;

	if (SRF_IS_FIRSTCALL())
	{
		MemoryContext fc_oldcontext;
		TupleDesc	fc_tupdesc;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		fc_tupdesc = CreateTemplateTupleDesc(5);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "word",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "catcode",
						   CHAROID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "barelabel",
						   BOOLOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4, "catdesc",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 5, "baredesc",
						   TEXTOID, -1, 0);

		fc_funcctx->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);

		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();

	if (fc_funcctx->call_cntr < ScanKeywords.num_keywords)
	{
		char	   *fc_values[5];
		HeapTuple	fc_tuple;

		/* cast-away-const 很丑，但替代方案也不多好 */
		fc_values[0] = unconstify(char *,
							   GetScanKeyword(fc_funcctx->call_cntr,
											  &ScanKeywords));

		switch (ScanKeywordCategories[fc_funcctx->call_cntr])
		{
			case UNRESERVED_KEYWORD:
				fc_values[1] = "U";
				fc_values[3] = _("unreserved");
				break;
			case COL_NAME_KEYWORD:
				fc_values[1] = "C";
				fc_values[3] = _("unreserved (cannot be function or type name)");
				break;
			case TYPE_FUNC_NAME_KEYWORD:
				fc_values[1] = "T";
				fc_values[3] = _("reserved (can be function or type name)");
				break;
			case RESERVED_KEYWORD:
				fc_values[1] = "R";
				fc_values[3] = _("reserved");
				break;
			default:			/* 不应该可能 */
				fc_values[1] = NULL;
				fc_values[3] = NULL;
				break;
		}

		if (ScanKeywordBareLabel[fc_funcctx->call_cntr])
		{
			fc_values[2] = "true";
			fc_values[4] = _("can be bare label");
		}
		else
		{
			fc_values[2] = "false";
			fc_values[4] = _("requires AS");
		}

		fc_tuple = BuildTupleFromCStrings(fc_funcctx->attinmeta, fc_values);

		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}

	SRF_RETURN_DONE(fc_funcctx);
}


/* 返回目录外键关系列表的函数 */
Datum pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	FmgrInfo   *fc_arrayinp;

	if (SRF_IS_FIRSTCALL())
	{
		MemoryContext fc_oldcontext;
		TupleDesc	fc_tupdesc;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		fc_tupdesc = CreateTemplateTupleDesc(6);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "fktable",
						   REGCLASSOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "fkcols",
						   TEXTARRAYOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "pktable",
						   REGCLASSOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4, "pkcols",
						   TEXTARRAYOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 5, "is_array",
						   BOOLOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 6, "is_opt",
						   BOOLOID, -1, 0);

		fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);

		/*
		 * 我们使用 array_in 将 sys_fk_relationships[] 中的 C 字符串
		 * 转换为文本数组。但我们不能使用 DirectFunctionCallN 来调用
		 * array_in，即使可以，那也不会很高效。填充一个
		 * FmgrInfo 用于调用。
		 */
		fc_arrayinp = (FmgrInfo *) palloc(sizeof(FmgrInfo));
		fmgr_info(F_ARRAY_IN, fc_arrayinp);
		fc_funcctx->user_fctx = fc_arrayinp;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_arrayinp = (FmgrInfo *) fc_funcctx->user_fctx;

	if (fc_funcctx->call_cntr < lengthof(sys_fk_relationships))
	{
		const SysFKRelationship *fc_fkrel = &sys_fk_relationships[fc_funcctx->call_cntr];
		Datum		fc_values[6];
		bool		fc_nulls[6];
		HeapTuple	fc_tuple;

		memset(fc_nulls, false, sizeof(fc_nulls));

		fc_values[0] = ObjectIdGetDatum(fc_fkrel->fk_table);
		fc_values[1] = FunctionCall3(fc_arrayinp,
								  CStringGetDatum(fc_fkrel->fk_columns),
								  ObjectIdGetDatum(TEXTOID),
								  Int32GetDatum(-1));
		fc_values[2] = ObjectIdGetDatum(fc_fkrel->pk_table);
		fc_values[3] = FunctionCall3(fc_arrayinp,
								  CStringGetDatum(fc_fkrel->pk_columns),
								  ObjectIdGetDatum(TEXTOID),
								  Int32GetDatum(-1));
		fc_values[4] = BoolGetDatum(fc_fkrel->is_array);
		fc_values[5] = BoolGetDatum(fc_fkrel->is_opt);

		fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);

		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}

	SRF_RETURN_DONE(fc_funcctx);
}


/*
 * 返回参数的类型。
 */
Datum pg_typeof(PG_FUNCTION_ARGS)
{
	PG_RETURN_OID(get_fn_expr_argtype(fcinfo->flinfo, 0));
}


/*
 * COLLATE FOR 表达式的实现；返回参数的排序规则。
 */
Datum pg_collation_for(PG_FUNCTION_ARGS)
{
	Oid			fc_typeid;
	Oid			fc_collid;

	fc_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
	if (!fc_typeid)
		PG_RETURN_NULL();
	if (!type_is_collatable(fc_typeid) && fc_typeid != UNKNOWNOID)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("collations are not supported by type %s",
						format_type_be(fc_typeid))));

	fc_collid = PG_GET_COLLATION();
	if (!fc_collid)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(fc_collid)));
}


/*
 * pg_relation_is_updatable - 确定指定的
 * 关系支持哪些更新事件。
 *
 * 这依赖于 rewriteHandler.c 中的 relation_is_updatable()，
 * 详情见该部分。
 */
Datum pg_relation_is_updatable(PG_FUNCTION_ARGS)
{
	Oid			fc_reloid = PG_GETARG_OID(0);
	bool		fc_include_triggers = PG_GETARG_BOOL(1);

	PG_RETURN_INT32(relation_is_updatable(fc_reloid, NIL, fc_include_triggers, NULL));
}

/*
 * pg_column_is_updatable - 确定列是否可更新
 *
 * 这个函数封装了关于信息_schema.columns.is_updatable 实际上
 * 意味着什么的决策。目前尚不清楚是否应要求
 * 列的关系的可删除性，因此我们希望该决策在 C 代码中，以便
 * 我们可以在不需要 initdb 的情况下进行更改。
 */
Datum pg_column_is_updatable(PG_FUNCTION_ARGS)
{
	Oid			fc_reloid = PG_GETARG_OID(0);
	AttrNumber	fc_attnum = PG_GETARG_INT16(1);
	AttrNumber	fc_col = fc_attnum - FirstLowInvalidHeapAttributeNumber;
	bool		fc_include_triggers = PG_GETARG_BOOL(2);
	int			fc_events;

	/* 系统列绝不可以更新 */
	if (fc_attnum <= 0)
		PG_RETURN_BOOL(false);

	fc_events = relation_is_updatable(fc_reloid, NIL, fc_include_triggers,
								   bms_make_singleton(fc_col));

	/* 我们要求关系的可更新性和可删除性 */
#define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))

	PG_RETURN_BOOL((fc_events & REQ_EVENTS) == REQ_EVENTS);
}


/*
 * 字符是有效的标识符起始字符吗？
 * 必须匹配 scan.l 的 {ident_start} 字符类。
 */
static bool fc_is_ident_start(unsigned char fc_c)
{
	/* 下划线和 ASCII 字母是可以的 */
	if (fc_c == '_')
		return true;
	if ((fc_c >= 'a' && fc_c <= 'z') || (fc_c >= 'A' && fc_c <= 'Z'))
		return true;
	/* 任何高位字符都可以（可能是多字节字符的一部分） */
	if (IS_HIGHBIT_SET(fc_c))
		return true;
	return false;
}

/*
 * 字符是有效的标识符续字符吗？
 * 必须匹配 scan.l 的 {ident_cont} 字符类。
 */
static bool fc_is_ident_cont(unsigned char fc_c)
{
	/* 可以是数字或美元符号... */
	if ((fc_c >= '0' && fc_c <= '9') || fc_c == '$')
		return true;
	/* ... 或者是标识符起始字符 */
	return fc_is_ident_start(fc_c);
}

/*
 * parse_ident - 将 SQL 限定标识符解析为多个独立标识符。
 * 当严格模式启动时（第二个参数），则不允许最后一个标识符之后的任何字符。
 */
Datum parse_ident(PG_FUNCTION_ARGS)
{
	text	   *fc_qualname = PG_GETARG_TEXT_PP(0);
	bool		fc_strict = PG_GETARG_BOOL(1);
	char	   *fc_qualname_str = text_to_cstring(fc_qualname);
	ArrayBuildState *fc_astate = NULL;
	char	   *fc_nextp;
	bool		fc_after_dot = false;

	/*
	 * 下面的代码在某些情况下会修改 qualname_str，因此我们应该
	 * 重新转换 qualname，如果我们需要在错误消息中显示原始字符串。
	 */
	fc_nextp = fc_qualname_str;

	/* 跳过前导空白 */
	while (scanner_isspace(*fc_nextp))
		fc_nextp++;

	for (;;)
	{
		char	   *fc_curname;
		bool		fc_missing_ident = true;

		if (*fc_nextp == '"')
		{
			char	   *fc_endp;

			fc_curname = fc_nextp + 1;
			for (;;)
			{
				fc_endp = strchr(fc_nextp + 1, '"');
				if (fc_endp == NULL)
#ifdef FDD//cppcheck
				{
#endif
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("string is not a valid identifier: \"%s\"",
									text_to_cstring(fc_qualname)),
							 errdetail("String has unclosed double quotes.")));
#ifdef FDD//cppcheck
					PG_RETURN_VOID();
				}
#endif
				if (fc_endp[1] != '"')
					break;
				memmove(fc_endp, fc_endp + 1, strlen(fc_endp));
				fc_nextp = fc_endp;
			}
			fc_nextp = fc_endp + 1;
			*fc_endp = '\0';

			if (fc_endp - fc_curname == 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("string is not a valid identifier: \"%s\"",
								text_to_cstring(fc_qualname)),
						 errdetail("Quoted identifier must not be empty.")));

			fc_astate = accumArrayResult(fc_astate, CStringGetTextDatum(fc_curname),
									  false, TEXTOID, CurrentMemoryContext);
			fc_missing_ident = false;
		}
		else if (fc_is_ident_start((unsigned char) *fc_nextp))
		{
			char	   *fc_downname;
			int			fc_len;
			text	   *fc_part;

			fc_curname = fc_nextp++;
			while (fc_is_ident_cont((unsigned char) *fc_nextp))
				fc_nextp++;

			fc_len = fc_nextp - fc_curname;

			/*
			 * 我们不会隐式截断标识符。这对于
			 * 允许用户检查标识符的特定部分长度过长非常有用。
			 * 用户很容易通过将我们的输出转换为 name[] 来获取
			 * 截断的名称。
			 */
			fc_downname = downcase_identifier(fc_curname, fc_len, false, false);
			fc_part = cstring_to_text_with_len(fc_downname, fc_len);
			fc_astate = accumArrayResult(fc_astate, PointerGetDatum(fc_part), false,
									  TEXTOID, CurrentMemoryContext);
			fc_missing_ident = false;
		}

		if (fc_missing_ident)
		{
			/* 根据失败的位置返回不同的错误消息。 */
			if (*fc_nextp == '.')
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("string is not a valid identifier: \"%s\"",
								text_to_cstring(fc_qualname)),
						 errdetail("No valid identifier before \".\".")));
			else if (fc_after_dot)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("string is not a valid identifier: \"%s\"",
								text_to_cstring(fc_qualname)),
						 errdetail("No valid identifier after \".\".")));
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("string is not a valid identifier: \"%s\"",
								text_to_cstring(fc_qualname))));
		}

		while (scanner_isspace(*fc_nextp))
			fc_nextp++;

		if (*fc_nextp == '.')
		{
			fc_after_dot = true;
			fc_nextp++;
			while (scanner_isspace(*fc_nextp))
				fc_nextp++;
		}
		else if (*fc_nextp == '\0')
		{
			break;
		}
		else
		{
			if (fc_strict)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("string is not a valid identifier: \"%s\"",
								text_to_cstring(fc_qualname))));
			break;
		}
	}

	PG_RETURN_DATUM(makeArrayResult(fc_astate, CurrentMemoryContext));
}

/*
 * pg_current_logfile
 *
 * 通过扫描 current_logfiles 报告 log collector 当前使用的日志文件。
 */
Datum pg_current_logfile(PG_FUNCTION_ARGS)
{
	FILE	   *fc_fd;
	char		fc_lbuffer[MAXPGPATH];
	char	   *fc_logfmt;

	/* 日志格式参数是可选的 */
	if (PG_NARGS() == 0 || PG_ARGISNULL(0))
		fc_logfmt = NULL;
	else
	{
		fc_logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0));

		if (strcmp(fc_logfmt, "stderr") != 0 &&
			strcmp(fc_logfmt, "csvlog") != 0 &&
			strcmp(fc_logfmt, "jsonlog") != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("log format \"%s\" is not supported", fc_logfmt),
					 errhint("The supported log formats are \"stderr\", \"csvlog\", and \"jsonlog\".")));
	}

	fc_fd = AllocateFile(LOG_METAINFO_DATAFILE, "r");
	if (fc_fd == NULL)
	{
		if (errno != ENOENT)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m",
							LOG_METAINFO_DATAFILE)));
		PG_RETURN_NULL();
	}

#ifdef WIN32
	/* syslogger.c 在 Windows 上写入 CRLF 行结束符 */
	_setmode(_fileno(fd), _O_TEXT);
#endif

	/*
	 * 读取文件以收集 syslogger 注册的当前日志文件名。
	 */
	while (fgets(fc_lbuffer, sizeof(fc_lbuffer), fc_fd) != NULL)
	{
		char	   *fc_log_format;
		char	   *fc_log_filepath;
		char	   *fc_nlpos;

		/* 从行中提取日志格式和日志文件路径。 */
		fc_log_format = fc_lbuffer;
		fc_log_filepath = strchr(fc_lbuffer, ' ');
		if (fc_log_filepath == NULL)
		{
			/* 哦哦。 未找到空格，因此文件内容已损坏。 */
			elog(ERROR,
				 "missing space character in \"%s\"", LOG_METAINFO_DATAFILE);
			break;
		}

		*fc_log_filepath = '\0';
		fc_log_filepath++;
		fc_nlpos = strchr(fc_log_filepath, '\n');
		if (fc_nlpos == NULL)
		{
			/* 哦哦。 未找到换行符，因此文件内容已损坏。 */
			elog(ERROR,
				 "missing newline character in \"%s\"", LOG_METAINFO_DATAFILE);
			break;
		}
		*fc_nlpos = '\0';

		if (fc_logfmt == NULL || strcmp(fc_logfmt, fc_log_format) == 0)
		{
			FreeFile(fc_fd);
			PG_RETURN_TEXT_P(cstring_to_text(fc_log_filepath));
		}
	}

	/* 关闭当前日志文件名文件。 */
	FreeFile(fc_fd);

	PG_RETURN_NULL();
}

/*
 * 报告 log collector 当前使用的日志文件（1 参数版本）
 *
 * 注意：此包装器是通过 opr_sanity 进行健全性检查所必需的，
 * 它检查所有共享实现 C 函数的内置函数是否包含相同数量的参数。
 */
Datum pg_current_logfile_1arg(PG_FUNCTION_ARGS)
{
	return pg_current_logfile(fcinfo);
}

/*
 * 针对 RelationGetReplicaIndex() 的 SQL 包装器。
 */
Datum pg_get_replica_identity_index(PG_FUNCTION_ARGS)
{
	Oid			fc_reloid = PG_GETARG_OID(0);
	Oid			fc_idxoid;
	Relation	fc_rel;

	fc_rel = table_open(fc_reloid, AccessShareLock);
	fc_idxoid = RelationGetReplicaIndex(fc_rel);
	table_close(fc_rel, AccessShareLock);

	if (OidIsValid(fc_idxoid))
		PG_RETURN_OID(fc_idxoid);
	else
		PG_RETURN_NULL();
}
